/* Copyright (c) 2020 XEPIC Corporation Limited */
/*
 * Copyright (c) 2003-2016 Tony Bybell.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#ifdef _AIX
#pragma alloca
#endif

#include "lxt2_write.h"

#include <config.h>

static char *lxt2_wr_vcd_truncate_bitvec(char *s) {
  char l, r;

  r = *s;
  if (r == '1') {
    return s;
  } else {
    s++;
  }

  for (;; s++) {
    l = r;
    r = *s;
    if (!r) return (s - 1);

    if (l != r) {
      return (((l == '0') && (r == '1')) ? s : s - 1);
    }
  }
}

/*
 * in-place sort to keep chained facs from migrating...
 */
static void wave_mergesort(struct lxt2_wr_symbol **a, struct lxt2_wr_symbol **b,
                           int lo, int hi) {
  int i, j, k;

  if (lo < hi) {
    int mid = (lo + hi) / 2;

    wave_mergesort(a, b, lo, mid);
    wave_mergesort(a, b, mid + 1, hi);

    i = 0;
    j = lo;
    while (j <= mid) {
      b[i++] = a[j++];
    }

    i = 0;
    k = lo;
    while ((k < j) && (j <= hi)) {
      if (strcmp(b[i]->name, a[j]->name) <= 0) {
        a[k++] = b[i++];
      } else {
        a[k++] = a[j++];
      }
    }

    while (k < j) {
      a[k++] = b[i++];
    }
  }
}

static void wave_msort(struct lxt2_wr_symbol **a, int num) {
  struct lxt2_wr_symbol **b = (struct lxt2_wr_symbol **)malloc(
      ((num / 2) + 1) * sizeof(struct lxt2_wr_symbol *));

  wave_mergesort(a, b, 0, num - 1);

  free(b);
}

/************************ splay ************************/

static lxt2_wr_ds_Tree *lxt2_wr_ds_splay(granmsk_t i, lxt2_wr_ds_Tree *t) {
  /* Simple top down splay, not requiring i to be in the tree t.  */
  /* What it does is described above.                             */
  lxt2_wr_ds_Tree N, *l, *r, *y;
  if (t == NULL) return t;
  N.left = N.right = NULL;
  l = r = &N;

  for (;;) {
    if (i < t->item) {
      if (t->left == NULL) break;
      if (i < t->left->item) {
        y = t->left; /* rotate right */
        t->left = y->right;
        y->right = t;
        t = y;
        if (t->left == NULL) break;
      }
      r->left = t; /* link right */
      r = t;
      t = t->left;
    } else if (i > t->item) {
      if (t->right == NULL) break;
      if (i > t->right->item) {
        y = t->right; /* rotate left */
        t->right = y->left;
        y->left = t;
        t = y;
        if (t->right == NULL) break;
      }
      l->right = t; /* link left */
      l = t;
      t = t->right;
    } else {
      break;
    }
  }
  l->right = t->left; /* assemble */
  r->left = t->right;
  t->left = N.right;
  t->right = N.left;
  return t;
}

static lxt2_wr_ds_Tree *lxt2_wr_ds_insert(granmsk_t i, lxt2_wr_ds_Tree *t,
                                          int val) {
  /* Insert i into the tree t, unless it's already there.    */
  /* Return a pointer to the resulting tree.                 */
  lxt2_wr_ds_Tree *n;

  n = (lxt2_wr_ds_Tree *)calloc(1, sizeof(lxt2_wr_ds_Tree));
  if (n == NULL) {
    fprintf(stderr, "ds_insert: ran out of memory, exiting.\n");
    exit(255);
  }
  n->item = i;
  n->val = val;
  if (t == NULL) {
    n->left = n->right = NULL;
    return n;
  }
  t = lxt2_wr_ds_splay(i, t);
  if (i < t->item) {
    n->left = t->left;
    n->right = t;
    t->left = NULL;
    return n;
  } else if (i > t->item) {
    n->right = t->right;
    n->left = t;
    t->right = NULL;
    return n;
  } else { /* We get here if it's already in the tree */
           /* Don't add it again                      */
    free(n);
    return t;
  }
}

/************************ splay ************************/

static int lxt2_wr_dslxt_success;

static lxt2_wr_dslxt_Tree *lxt2_wr_dslxt_splay(char *i, lxt2_wr_dslxt_Tree *t) {
  /* Simple top down splay, not requiring i to be in the tree t.  */
  /* What it does is described above.                             */
  lxt2_wr_dslxt_Tree N, *l, *r, *y;
  int dir;

  lxt2_wr_dslxt_success = 0;

  if (t == NULL) return t;
  N.left = N.right = NULL;
  l = r = &N;

  for (;;) {
    dir = strcmp(i, t->item);
    if (dir < 0) {
      if (t->left == NULL) break;
      if (strcmp(i, t->left->item) < 0) {
        y = t->left; /* rotate right */
        t->left = y->right;
        y->right = t;
        t = y;
        if (t->left == NULL) break;
      }
      r->left = t; /* link right */
      r = t;
      t = t->left;
    } else if (dir > 0) {
      if (t->right == NULL) break;
      if (strcmp(i, t->right->item) > 0) {
        y = t->right; /* rotate left */
        t->right = y->left;
        y->left = t;
        t = y;
        if (t->right == NULL) break;
      }
      l->right = t; /* link left */
      l = t;
      t = t->right;
    } else {
      lxt2_wr_dslxt_success = 1;
      break;
    }
  }
  l->right = t->left; /* assemble */
  r->left = t->right;
  t->left = N.right;
  t->right = N.left;
  return t;
}

static lxt2_wr_dslxt_Tree *lxt2_wr_dslxt_insert(char *i, lxt2_wr_dslxt_Tree *t,
                                                unsigned int val) {
  /* Insert i into the tree t, unless it's already there.    */
  /* Return a pointer to the resulting tree.                 */
  lxt2_wr_dslxt_Tree *n;
  int dir;

  n = (lxt2_wr_dslxt_Tree *)calloc(1, sizeof(lxt2_wr_dslxt_Tree));
  if (n == NULL) {
    fprintf(stderr, "dslxt_insert: ran out of memory, exiting.\n");
    exit(255);
  }
  n->item = i;
  n->val = val;
  if (t == NULL) {
    n->left = n->right = NULL;
    return n;
  }
  t = lxt2_wr_dslxt_splay(i, t);
  dir = strcmp(i, t->item);
  if (dir < 0) {
    n->left = t->left;
    n->right = t;
    t->left = NULL;
    return n;
  } else if (dir > 0) {
    n->right = t->right;
    n->left = t;
    t->right = NULL;
    return n;
  } else { /* We get here if it's already in the tree */
           /* Don't add it again                      */
    free(n);
    return t;
  }
}

/************************ splay ************************/

/*
 * functions which emit various big endian
 * data to a file
 */
static int lxt2_wr_emit_u8(struct lxt2_wr_trace *lt, int value) {
  unsigned char buf[1];
  int nmemb;

  buf[0] = value & 0xff;
  nmemb = fwrite(buf, sizeof(char), 1, lt->handle);
  lt->position += nmemb;
  return (nmemb);
}

static int lxt2_wr_emit_u16(struct lxt2_wr_trace *lt, int value) {
  unsigned char buf[2];
  int nmemb;

  buf[0] = (value >> 8) & 0xff;
  buf[1] = value & 0xff;
  nmemb = fwrite(buf, sizeof(char), 2, lt->handle);
  lt->position += nmemb;
  return (nmemb);
}

static int lxt2_wr_emit_u32(struct lxt2_wr_trace *lt, int value) {
  unsigned char buf[4];
  int nmemb;

  buf[0] = (value >> 24) & 0xff;
  buf[1] = (value >> 16) & 0xff;
  buf[2] = (value >> 8) & 0xff;
  buf[3] = value & 0xff;
  nmemb = fwrite(buf, sizeof(char), 4, lt->handle);
  lt->position += nmemb;
  return (nmemb);
}

static int lxt2_wr_emit_u64(struct lxt2_wr_trace *lt, int valueh, int valuel) {
  int rc;

  if ((rc = lxt2_wr_emit_u32(lt, valueh))) {
    rc = lxt2_wr_emit_u32(lt, valuel);
  }

  return (rc);
}

/*
 * gzfunctions which emit various big endian
 * data to a file.  (lt->position needs to be
 * fixed up on gzclose so the tables don't
 * get out of sync!)
 */
static int gzwrite_buffered(struct lxt2_wr_trace *lt) {
  int rc = 1;

  if (lt->gzbufpnt > LXT2_WR_GZWRITE_BUFFER) {
    rc = gzwrite(lt->zhandle, lt->gzdest, lt->gzbufpnt);
    rc = rc ? 1 : 0;
    lt->gzbufpnt = 0;
  }

  return (rc);
}

static void gzflush_buffered(struct lxt2_wr_trace *lt, int doclose) {
  if (lt->gzbufpnt) {
    gzwrite(lt->zhandle, lt->gzdest, lt->gzbufpnt);
    lt->gzbufpnt = 0;
    if (!doclose) {
      gzflush(lt->zhandle, Z_SYNC_FLUSH);
    }
  }

  if (doclose) {
    gzclose(lt->zhandle);
  }
}

static int lxt2_wr_emit_u8z(struct lxt2_wr_trace *lt, int value) {
  int nmemb;

  lt->gzdest[lt->gzbufpnt++] = value & 0xff;

  nmemb = gzwrite_buffered(lt);
  lt->zpackcount++;
  lt->position++;
  return (nmemb);
}

static int lxt2_wr_emit_u16z(struct lxt2_wr_trace *lt, int value) {
  int nmemb;

  lt->gzdest[lt->gzbufpnt++] = (value >> 8) & 0xff;
  lt->gzdest[lt->gzbufpnt++] = value & 0xff;
  nmemb = gzwrite_buffered(lt);
  lt->zpackcount += 2;
  lt->position += 2;
  return (nmemb);
}

static int lxt2_wr_emit_u24z(struct lxt2_wr_trace *lt, int value) {
  int nmemb;

  lt->gzdest[lt->gzbufpnt++] = (value >> 16) & 0xff;
  lt->gzdest[lt->gzbufpnt++] = (value >> 8) & 0xff;
  lt->gzdest[lt->gzbufpnt++] = value & 0xff;
  nmemb = gzwrite_buffered(lt);
  lt->zpackcount += 3;
  lt->position += 3;
  return (nmemb);
}

static int lxt2_wr_emit_u32z(struct lxt2_wr_trace *lt, int value) {
  int nmemb;

  lt->gzdest[lt->gzbufpnt++] = (value >> 24) & 0xff;
  lt->gzdest[lt->gzbufpnt++] = (value >> 16) & 0xff;
  lt->gzdest[lt->gzbufpnt++] = (value >> 8) & 0xff;
  lt->gzdest[lt->gzbufpnt++] = value & 0xff;
  nmemb = gzwrite_buffered(lt);

  lt->zpackcount += 4;
  lt->position += 4;
  return (nmemb);
}

static int lxt2_wr_emit_u64z(struct lxt2_wr_trace *lt, int valueh, int valuel) {
  int rc;

  if ((rc = lxt2_wr_emit_u32z(lt, valueh))) {
    rc = lxt2_wr_emit_u32z(lt, valuel);
  }

  return (rc);
}

static int lxt2_wr_emit_stringz(struct lxt2_wr_trace *lt, char *value) {
  int rc = 1;
  do {
    rc &= lxt2_wr_emit_u8z(lt, *value);
  } while (*(value++));
  return (rc);
}

/*
 * hash/symtable manipulation
 */
static int lxt2_wr_hash(const char *s) {
  const char *p;
  char ch;
  unsigned int h = 0, h2 = 0, pos = 0, g;
  for (p = s; *p; p++) {
    ch = *p;
    h2 <<= 3;
    h2 -= ((unsigned int)ch +
           (pos++)); /* this handles stranded vectors quite well.. */

    h = (h << 4) + ch;
    if ((g = h & 0xf0000000)) {
      h = h ^ (g >> 24);
      h = h ^ g;
    }
  }

  h ^= h2; /* combine the two hashes */
  return (h % LXT2_WR_SYMPRIME);
}

static struct lxt2_wr_symbol *lxt2_wr_symadd(struct lxt2_wr_trace *lt,
                                             const char *name, int hv) {
  struct lxt2_wr_symbol *s;

  s = (struct lxt2_wr_symbol *)calloc(1, sizeof(struct lxt2_wr_symbol));
  strcpy(s->name = (char *)malloc((s->namlen = strlen(name)) + 1), name);
  s->next = lt->sym[hv];
  lt->sym[hv] = s;
  return (s);
}

static struct lxt2_wr_symbol *lxt2_wr_symfind(struct lxt2_wr_trace *lt,
                                              const char *s) {
  int hv;
  struct lxt2_wr_symbol *temp;

  hv = lxt2_wr_hash(s);
  if (!(temp = lt->sym[hv]))
    return (NULL); /* no hash entry, add here wanted to add */

  while (temp) {
    if (!strcmp(temp->name, s)) {
      return (temp); /* in table already */
    }
    if (!temp->next) break;
    temp = temp->next;
  }

  return (NULL); /* not found, add here if you want to add*/
}

/*
 * compress facs to a prefix count + string + 0x00
 */
static void lxt2_wr_compress_fac(struct lxt2_wr_trace *lt, char *str) {
  int i;
  int len = strlen(str);
  int minlen = (len < lt->compress_fac_len) ? len : lt->compress_fac_len;

  if (minlen > 65535)
    minlen = 65535; /* keep in printable range--most hierarchies won't be this
                       big anyway */

  if (lt->compress_fac_str) {
    for (i = 0; i < minlen; i++) {
      if (lt->compress_fac_str[i] != str[i]) break;
    }
    lxt2_wr_emit_u16z(lt, i);
    lxt2_wr_emit_stringz(lt, str + i);
    free(lt->compress_fac_str);
  } else {
    lxt2_wr_emit_u16z(lt, 0);
    lxt2_wr_emit_stringz(lt, str);
  }

  lt->compress_fac_str = (char *)malloc((lt->compress_fac_len = len) + 1);
  strcpy(lt->compress_fac_str, str);
}

/*
 * emit facs in sorted order along with geometry
 * and sync table info
 */

static void strip_brack(struct lxt2_wr_symbol *s) {
  char *lastch = s->name + s->namlen - 1;
  if (*lastch != ']') return;
  if (s->namlen < 3) return;
  lastch--;
  while (lastch != s->name) {
    if (*lastch == '.') {
      return; /* MTI SV [0.3] notation for implicit vars */
    }

    if (*lastch == '[') {
      *lastch = 0x00;
      return;
    }
    lastch--;
  }
  return;
}

static void lxt2_wr_emitfacs(struct lxt2_wr_trace *lt) {
  unsigned int i;

  if ((lt) && (lt->numfacs)) {
    struct lxt2_wr_symbol *s = lt->symchain;
    struct lxt2_wr_symbol **aliascache = (struct lxt2_wr_symbol **)calloc(
        lt->numalias ? lt->numalias : 1, sizeof(struct lxt2_wr_symbol *));
    unsigned int aliases_encountered, facs_encountered;

    lt->sorted_facs = (struct lxt2_wr_symbol **)calloc(
        lt->numfacs, sizeof(struct lxt2_wr_symbol *));

    if (lt->sorted_facs && aliascache) {
      if (lt->do_strip_brackets)
        for (i = 0; i < lt->numfacs; i++) {
          lt->sorted_facs[lt->numfacs - i - 1] =
              s; /* facs were chained backwards so reverse to restore bitslicing
                  */
          strip_brack(s);
          s = s->symchain;
        }
      else
        for (i = 0; i < lt->numfacs; i++) {
          lt->sorted_facs[lt->numfacs - i - 1] =
              s; /* facs were chained backwards so reverse to restore bitslicing
                  */
          s = s->symchain;
        }
      wave_msort(lt->sorted_facs, lt->numfacs);

      if (lt->partial_preference) {
        /* move preferenced facs up */
        struct lxt2_wr_symbol **prefcache = aliascache;
        int prefs_encountered = 0;

        facs_encountered = 0;
        for (i = 0; i < lt->numfacs; i++) {
          if ((lt->sorted_facs[i]->partial_preference) == 0) {
            lt->sorted_facs[facs_encountered] = lt->sorted_facs[i];
            facs_encountered++;
          } else {
            prefcache[prefs_encountered] = lt->sorted_facs[i];
            prefs_encountered++;
          }
        }
        /* then append the non-preferenced facs */
        for (i = 0; i < facs_encountered; i++) {
          prefcache[prefs_encountered + i] = lt->sorted_facs[i];
        }

        /* now make prefcache the main cache */
        aliascache = lt->sorted_facs;
        lt->sorted_facs = prefcache;
      }

      /* move facs up */
      aliases_encountered = 0, facs_encountered = 0;
      for (i = 0; i < lt->numfacs; i++) {
        if ((lt->sorted_facs[i]->flags & LXT2_WR_SYM_F_ALIAS) == 0) {
          lt->sorted_facs[facs_encountered] = lt->sorted_facs[i];
          facs_encountered++;
        } else {
          aliascache[aliases_encountered] = lt->sorted_facs[i];
          aliases_encountered++;
        }
      }
      /* then append the aliases */
      for (i = 0; i < aliases_encountered; i++) {
        lt->sorted_facs[facs_encountered + i] = aliascache[i];
      }

      for (i = 0; i < lt->numfacs; i++) {
        lt->sorted_facs[i]->facnum = i;
      }

      if (!lt->timezero) {
        lxt2_wr_emit_u32(lt, lt->numfacs); /* uncompressed */
      } else {
        lxt2_wr_emit_u32(lt,
                         0); /* uncompressed, flag to insert extra parameters */
        lxt2_wr_emit_u32(lt, 8); /* uncompressed 8 counts timezero and on */
        lxt2_wr_emit_u32(lt, lt->numfacs); /* uncompressed */
        lxt2_wr_emit_u64(lt, (lt->timezero >> 32) & 0xffffffffL,
                         lt->timezero & 0xffffffffL); /* uncompressed */
      }

      lxt2_wr_emit_u32(lt, lt->numfacbytes); /* uncompressed */
      lxt2_wr_emit_u32(lt, lt->longestname); /* uncompressed */

      lt->facname_offset = lt->position;

      lxt2_wr_emit_u32(lt,
                       0); /* uncompressed : placeholder for zfacnamesize     */
      lxt2_wr_emit_u32(
          lt, 0); /* uncompressed : placeholder for zfacname_predec_size */
      lxt2_wr_emit_u32(lt,
                       0); /* uncompressed : placeholder for zfacgeometrysize */
      lxt2_wr_emit_u8(lt, lt->timescale); /* timescale (-9 default == nsec) */

      fflush(lt->handle);
      lt->zfacname_size = lt->position;
      lt->zhandle = gzdopen(dup(fileno(lt->handle)), "wb9");

      lt->zpackcount = 0;
      for (i = 0; i < lt->numfacs; i++) {
        lxt2_wr_compress_fac(lt, lt->sorted_facs[i]->name);
        free(lt->sorted_facs[i]->name);
        lt->sorted_facs[i]->name = NULL;
      }
      free(lt->compress_fac_str);
      lt->compress_fac_str = NULL;
      lt->compress_fac_len = 0;
      lt->zfacname_predec_size = lt->zpackcount;

      gzflush_buffered(lt, 1);
      fseeko(lt->handle, 0L, SEEK_END);
      lt->position = ftello(lt->handle);
      lt->zfacname_size = lt->position - lt->zfacname_size;

      lt->zhandle = gzdopen(dup(fileno(lt->handle)), "wb9");

      lt->facgeometry_offset = lt->position;
      for (i = 0; i < lt->numfacs; i++) {
        if ((lt->sorted_facs[i]->flags & LXT2_WR_SYM_F_ALIAS) == 0) {
          lxt2_wr_emit_u32z(lt, lt->sorted_facs[i]->rows);
          lxt2_wr_emit_u32z(lt, lt->sorted_facs[i]->msb);
          lxt2_wr_emit_u32z(lt, lt->sorted_facs[i]->lsb);
          lxt2_wr_emit_u32z(lt, lt->sorted_facs[i]->flags);
        } else {
          lxt2_wr_emit_u32z(lt, lt->sorted_facs[i]->aliased_to->facnum);
          lxt2_wr_emit_u32z(lt, lt->sorted_facs[i]->msb);
          lxt2_wr_emit_u32z(lt, lt->sorted_facs[i]->lsb);
          lxt2_wr_emit_u32z(lt, LXT2_WR_SYM_F_ALIAS);
        }
      }

      gzflush_buffered(lt, 1);
      fseeko(lt->handle, 0L, SEEK_END);
      lt->position = ftello(lt->handle);
      lt->break_header_size = lt->position; /* in case we need to emit multiple
                                               lxt2s with same header */
      lt->zfacgeometry_size = lt->position - lt->facgeometry_offset;

      fseeko(lt->handle, lt->facname_offset, SEEK_SET);
      lxt2_wr_emit_u32(lt, lt->zfacname_size); /* backpatch sizes... */
      lxt2_wr_emit_u32(lt, lt->zfacname_predec_size);
      lxt2_wr_emit_u32(lt, lt->zfacgeometry_size);

      lt->numfacs =
          facs_encountered; /* don't process alias value changes ever */
    }

    free(aliascache);
  }
}

/*
 * initialize the trace and get back an lt context
 */
struct lxt2_wr_trace *lxt2_wr_init(const char *name) {
  struct lxt2_wr_trace *lt =
      (struct lxt2_wr_trace *)calloc(1, sizeof(struct lxt2_wr_trace));

  if ((!name) || (!(lt->handle = fopen(name, "wb")))) {
    free(lt);
    lt = NULL;
  } else {
    lt->lxtname = strdup(name);

    lxt2_wr_emit_u16(lt, LXT2_WR_HDRID);
    lxt2_wr_emit_u16(lt, LXT2_WR_VERSION);
    lxt2_wr_emit_u8(lt, LXT2_WR_GRANULE_SIZE); /* currently 32 or 64 */
    lt->timescale = -9;
    lt->maxgranule = LXT2_WR_GRANULE_NUM;
    lxt2_wr_set_compression_depth(lt,
                                  4); /* set fast/loose compression depth, user
                                         can fix this any time after init */
    lt->initial_value = 'x';
  }

  return (lt);
}

/*
 * setting break size
 */
void lxt2_wr_set_break_size(struct lxt2_wr_trace *lt, off_t siz) {
  if (lt) {
    lt->break_size = siz;
  }
}

/*
 * enable/disable partial dump mode (for faster reads)
 */
void lxt2_wr_set_partial_off(struct lxt2_wr_trace *lt) {
  if (lt) {
    lt->partial = 0;
    lt->partial_zip = 0;
  }
}

void lxt2_wr_set_partial_on(struct lxt2_wr_trace *lt, int zipmode) {
  if (lt) {
    lt->partial = 1;
    lt->partial_zip = (zipmode != 0);
    lt->partial_iter = LXT2_WR_PARTIAL_SIZE;
  }
}

void lxt2_wr_set_partial_preference(struct lxt2_wr_trace *lt,
                                    const char *name) {
  struct lxt2_wr_symbol *s;

  if ((lt) && (name) && (!lt->sorted_facs)) {
    s = lxt2_wr_symfind(lt, name);
    if (s) {
      while (s->aliased_to) /* find root alias */
      {
        s = s->aliased_to;
      }

      s->partial_preference = 1;
    }
  }
}

/*
 * enable/disable checkpointing (for smaller files)
 */
void lxt2_wr_set_checkpoint_off(struct lxt2_wr_trace *lt) {
  if (lt) {
    lt->no_checkpoint = 1;
  }
}

void lxt2_wr_set_checkpoint_on(struct lxt2_wr_trace *lt) {
  if (lt) {
    lt->no_checkpoint = 0;
  }
}

/*
 * set initial value of trace (0, 1, x, z) only legal vals
 */
void lxt2_wr_set_initial_value(struct lxt2_wr_trace *lt, char value) {
  if (lt) {
    switch (value) {
      case '0':
      case '1':
      case 'x':
      case 'z':
        break;
      case 'Z':
        value = 'z';
        break;
      default:
        value = 'x';
        break;
    }

    lt->initial_value = value;
  }
}

/*
 * maint function for finding a symbol if it exists
 */
struct lxt2_wr_symbol *lxt2_wr_symbol_find(struct lxt2_wr_trace *lt,
                                           const char *name) {
  struct lxt2_wr_symbol *s = NULL;

  if ((lt) && (name)) s = lxt2_wr_symfind(lt, name);
  return (s);
}

/*
 * add a trace (if it doesn't exist already)
 */
struct lxt2_wr_symbol *lxt2_wr_symbol_add(struct lxt2_wr_trace *lt,
                                          const char *name, unsigned int rows,
                                          int msb, int lsb, int flags) {
  struct lxt2_wr_symbol *s;
  int len;
  int flagcnt;

  if ((!lt) || (lt->sorted_facs)) return (NULL);

  flagcnt = ((flags & LXT2_WR_SYM_F_INTEGER) != 0) +
            ((flags & LXT2_WR_SYM_F_DOUBLE) != 0) +
            ((flags & LXT2_WR_SYM_F_STRING) != 0);

  if ((flagcnt > 1) || (!lt) || (!name) || (lxt2_wr_symfind(lt, name)))
    return (NULL);

  s = lxt2_wr_symadd(lt, name, lxt2_wr_hash(name));
  s->rows = rows;
  s->flags =
      flags & (~LXT2_WR_SYM_F_ALIAS); /* aliasing makes no sense here.. */

  if (!flagcnt) {
    s->msb = msb;
    s->lsb = lsb;
    s->len = (msb < lsb) ? (lsb - msb + 1) : (msb - lsb + 1);
  }

  if (flags & LXT2_WR_SYM_F_DOUBLE) {
    s->value = strdup("NaN");
  } else {
    if (flags & LXT2_WR_SYM_F_INTEGER) {
      s->len = 32;
    }

    s->value = (char *)malloc(s->len + 1);
    memset(s->value, lt->initial_value, s->len);
    s->value[s->len] = 0;

    s->msk = LXT2_WR_GRAN_1VAL; /* stuff in an initial value */
    switch (lt->initial_value) {
      case '0':
        s->chg[0] = LXT2_WR_ENC_0;
        break;
      case '1':
        s->chg[0] = LXT2_WR_ENC_1;
        break;
      case 'z':
        s->chg[0] = LXT2_WR_ENC_Z;
        break;
      default:
        s->chg[0] = LXT2_WR_ENC_X;
        break;
    }
    s->chgpos++; /* don't worry that a time doesn't exist as it will soon
                    enough.. */
  }

  s->symchain = lt->symchain;
  lt->symchain = s;

  lt->numfacs++;

  if ((len = strlen(name)) > lt->longestname) lt->longestname = len;
  lt->numfacbytes += (len + 1);

  return (s);
}

/*
 * add an alias trace (if it doesn't exist already and orig is found)
 */
struct lxt2_wr_symbol *lxt2_wr_symbol_alias(struct lxt2_wr_trace *lt,
                                            const char *existing_name,
                                            const char *alias, int msb,
                                            int lsb) {
  struct lxt2_wr_symbol *s, *sa;
  int len;
  int bitlen;
  int flagcnt;

  if ((!lt) || (!existing_name) || (!alias) ||
      (!(s = lxt2_wr_symfind(lt, existing_name))) ||
      (lxt2_wr_symfind(lt, alias)))
    return (NULL);

  if (lt->sorted_facs) return (NULL);

  while (s->aliased_to) /* find root alias */
  {
    s = s->aliased_to;
  }

  flagcnt = ((s->flags & LXT2_WR_SYM_F_INTEGER) != 0) +
            ((s->flags & LXT2_WR_SYM_F_DOUBLE) != 0) +
            ((s->flags & LXT2_WR_SYM_F_STRING) != 0);
  bitlen = (msb < lsb) ? (lsb - msb + 1) : (msb - lsb + 1);
  if ((!flagcnt) && (bitlen != s->len)) return (NULL);

  sa = lxt2_wr_symadd(lt, alias, lxt2_wr_hash(alias));
  sa->flags = LXT2_WR_SYM_F_ALIAS; /* only point this can get set */
  sa->aliased_to = s;

  if (!flagcnt) {
    sa->msb = msb;
    sa->lsb = lsb;
    sa->len = bitlen;
  }

  sa->symchain = lt->symchain;
  lt->symchain = sa;
  lt->numfacs++;
  lt->numalias++;
  if ((len = strlen(alias)) > lt->longestname) lt->longestname = len;
  lt->numfacbytes += (len + 1);

  return (sa);
}

/*
 * set current time/granule updating
 */
int lxt2_wr_inc_time_by_delta(struct lxt2_wr_trace *lt, unsigned int timeval) {
  return (lxt2_wr_set_time64(lt, lt->maxtime + (lxttime_t)timeval));
}

int lxt2_wr_set_time(struct lxt2_wr_trace *lt, unsigned int timeval) {
  return (lxt2_wr_set_time64(lt, (lxttime_t)timeval));
}

int lxt2_wr_inc_time_by_delta64(struct lxt2_wr_trace *lt, lxttime_t timeval) {
  return (lxt2_wr_set_time64(lt, lt->maxtime + timeval));
}

/*
 * file size limiting/header cloning...
 */
static void lxt2_wr_emit_do_breakfile(struct lxt2_wr_trace *lt) {
  unsigned int len = strlen(lt->lxtname);
  int i;
  char *tname = (char *)malloc(len + 30);
  FILE *f2, *clone;
  off_t cnt, seg;
  char buf[32768];

  for (i = len; i > 0; i--) {
    if (lt->lxtname[i] == '.') break;
  }

  if (!i) {
    sprintf(tname, "%s_%03u.lxt", lt->lxtname, ++lt->break_number);
  } else {
    memcpy(tname, lt->lxtname, i);
    sprintf(tname + i, "_%03u.lxt", ++lt->break_number);
  }

  f2 = fopen(tname, "wb");
  if (!f2) /* if error, keep writing to same output file...sorry */
  {
    free(tname);
    return;
  }

  clone = fopen(lt->lxtname, "rb");
  if (!clone) { /* this should never happen */
    fclose(f2);
    unlink(tname);
    free(tname);
    return;
  }

  /* clone original header */
  for (cnt = 0; cnt < lt->break_header_size; cnt += sizeof(buf)) {
    seg = lt->break_header_size - cnt;
    if (seg > (off_t)sizeof(buf)) {
      seg = sizeof(buf);
    }

    if (fread(buf, seg, 1, clone)) {
      if (!fwrite(buf, seg, 1, f2)) break; /* write error! */
    }
  }

  fclose(clone);
  fclose(lt->handle);
  lt->handle = f2;
  free(tname);
}

/*
 * emit granule
 */
void lxt2_wr_flush_granule(struct lxt2_wr_trace *lt, int do_finalize) {
  unsigned int idx_nbytes, map_nbytes, i, j;
  struct lxt2_wr_symbol *s;
  unsigned int partial_iter;
  unsigned int iter, iter_hi;
  unsigned char using_partial, using_partial_zip = 0;
  off_t current_iter_pos = 0;
  int early_flush;

  if (lt->flush_valid) {
    if (lt->flushtime == lt->lasttime) {
      return;
    }

    lt->flush_valid = 0;
  }

  lt->granule_dirty = 0;

  if ((using_partial = (lt->partial) && (lt->numfacs > lt->partial_iter))) {
    partial_iter = lt->partial_iter;
    using_partial_zip = lt->partial_zip;
  } else {
    partial_iter = lt->numfacs;
  }

  if (!lt->timegranule) {
    int attempt_break_state = 2;

    do {
      fseeko(lt->handle, 0L, SEEK_END);
      lt->current_chunk = lt->position = ftello(lt->handle);

      if ((lt->break_size) && (attempt_break_state == 2) &&
          (lt->position >= lt->break_size) &&
          (lt->position != lt->break_header_size)) {
        lxt2_wr_emit_do_breakfile(lt);
        attempt_break_state--;
      } else {
        attempt_break_state = 0;
      }
    } while (attempt_break_state);

    /* fprintf(stderr, "First chunk position is %d (0x%08x)\n",
     * lt->current_chunk, lt->current_chunk); */
    lxt2_wr_emit_u32(lt, 0);    /* size of this section (uncompressed) */
    lxt2_wr_emit_u32(lt, 0);    /* size of this section (compressed)   */
    lxt2_wr_emit_u64(lt, 0, 0); /* begin time of section               */
    lxt2_wr_emit_u64(lt, 0, 0); /* end time of section                 */
    fflush(lt->handle);
    lt->current_chunkz = lt->position;
    /* fprintf(stderr, "First chunkz position is %d (0x%08x)\n",
     * lt->current_chunkz, lt->current_chunkz); */

    if (!using_partial_zip) {
      lt->zhandle = gzdopen(dup(fileno(lt->handle)), lt->zmode);
    } else {
      lt->zpackcount_cumulative = 0;
    }
    lt->zpackcount = 0;
  }

  for (iter = 0; iter < lt->numfacs; iter = iter_hi) {
    unsigned int total_chgs;
    unsigned int partial_length;

    total_chgs = 0;
    /* partial_length = 0; */ /* scan-build : never read */

    iter_hi = iter + partial_iter;
    if (iter_hi > lt->numfacs) iter_hi = lt->numfacs;

    for (j = iter; j < iter_hi; j++) {
      granmsk_t msk = lt->sorted_facs[j]->msk;

      lt->mapdict = lxt2_wr_ds_splay(msk, lt->mapdict);
      if ((!lt->mapdict) || (lt->mapdict->item != msk)) {
        lt->mapdict = lxt2_wr_ds_insert(msk, lt->mapdict, lt->num_map_entries);
        lt->num_map_entries++;

        if (lt->mapdict_curr) {
          lt->mapdict_curr->next = lt->mapdict;
          lt->mapdict_curr = lt->mapdict;
        } else {
          lt->mapdict_head = lt->mapdict_curr = lt->mapdict;
        }
      }
    }

    if (lt->num_map_entries <= 256) {
      map_nbytes = 1;
    } else if (lt->num_map_entries <= 256 * 256) {
      map_nbytes = 2;
    } else if (lt->num_map_entries <= 256 * 256 * 256) {
      map_nbytes = 3;
    } else {
      map_nbytes = 4;
    }

    if ((lt->num_dict_entries + LXT2_WR_DICT_START) <= 256) {
      idx_nbytes = 1;
    } else if ((lt->num_dict_entries + LXT2_WR_DICT_START) <= 256 * 256) {
      idx_nbytes = 2;
    } else if ((lt->num_dict_entries + LXT2_WR_DICT_START) <= 256 * 256 * 256) {
      idx_nbytes = 3;
    } else {
      idx_nbytes = 4;
    }

    if (using_partial) {
      /* skip */
      partial_length = 1 +                               /* lt->timepos */
                       lt->timepos * sizeof(lxttime_t) + /* timevals */

                       1 +                             /* map_nbytes */
                       (iter_hi - iter) * map_nbytes + /* actual map */
                       1;                              /* idx_nbytes */

      for (j = iter; j < iter_hi; j++) {
        s = lt->sorted_facs[j];
        total_chgs += s->chgpos;
      }
      total_chgs *= idx_nbytes; /* vch skip */

      partial_length += total_chgs; /* actual changes */

      if (using_partial_zip) {
        fseeko(lt->handle, 0L, SEEK_END);
        current_iter_pos = ftello(lt->handle);
        lxt2_wr_emit_u32(lt, 0); /* size of this section (compressed)   */
        lxt2_wr_emit_u32(
            lt, partial_length + 9); /* size of this section (uncompressed) */
        lxt2_wr_emit_u32(lt, iter);  /* begin iter of section               */
        fflush(lt->handle);

        lt->zhandle = gzdopen(dup(fileno(lt->handle)), lt->zmode);
        lt->zpackcount = 0;
      }

      lxt2_wr_emit_u8z(lt, LXT2_WR_GRAN_SECT_TIME_PARTIAL);
      lxt2_wr_emit_u32z(lt, iter);
      lxt2_wr_emit_u32z(lt, partial_length);
    } else {
      lxt2_wr_emit_u8z(lt, LXT2_WR_GRAN_SECT_TIME);
    }

    lxt2_wr_emit_u8z(lt, lt->timepos);
    for (i = 0; i < lt->timepos; i++) {
      lxt2_wr_emit_u64z(lt, (lt->timetable[i] >> 32) & 0xffffffff,
                        lt->timetable[i] & 0xffffffff);
    }
    gzflush_buffered(lt, 0);

    lxt2_wr_emit_u8z(lt, map_nbytes);
    for (j = iter; j < iter_hi; j++) {
      unsigned int val;
      s = lt->sorted_facs[j];
      lt->mapdict = lxt2_wr_ds_splay(s->msk, lt->mapdict);
      val = lt->mapdict->val;

      switch (map_nbytes) {
        case 1:
          lxt2_wr_emit_u8z(lt, val);
          break;
        case 2:
          lxt2_wr_emit_u16z(lt, val);
          break;
        case 3:
          lxt2_wr_emit_u24z(lt, val);
          break;
        case 4:
          lxt2_wr_emit_u32z(lt, val);
          break;
      }

      s->msk = LXT2_WR_GRAN_0VAL;
    }

    lxt2_wr_emit_u8z(lt, idx_nbytes);
    gzflush_buffered(lt, 0);
    for (j = iter; j < iter_hi; j++) {
      s = lt->sorted_facs[j];

      for (i = 0; i < s->chgpos; i++) {
        switch (idx_nbytes) {
          case 1:
            lxt2_wr_emit_u8z(lt, s->chg[i]);
            break;
          case 2:
            lxt2_wr_emit_u16z(lt, s->chg[i]);
            break;
          case 3:
            lxt2_wr_emit_u24z(lt, s->chg[i]);
            break;
          case 4:
            lxt2_wr_emit_u32z(lt, s->chg[i]);
            break;
        }
      }

      s->chgpos = 0;
    }

    if (using_partial_zip) {
      off_t clen;

      gzflush_buffered(lt, 1);
      fseeko(lt->handle, 0L, SEEK_END);
      lt->position = ftello(lt->handle);

      clen = lt->position - current_iter_pos - 12;
      fseeko(lt->handle, current_iter_pos, SEEK_SET);

      lt->zpackcount_cumulative += lt->zpackcount;

      lxt2_wr_emit_u32(lt, clen);
    } else {
      gzflush_buffered(lt, 0);
    }
  } /* ...for(iter) */

  lt->timepos = 0;
  lt->timegranule++;

  if (lt->break_size) {
    early_flush = (ftello(lt->handle) >= lt->break_size);
  } else {
    early_flush = 0;
  }

  if ((lt->timegranule >= lt->maxgranule) || (do_finalize) || (early_flush)) {
    off_t unclen, clen;
    lxt2_wr_ds_Tree *dt, *dt2;
    lxt2_wr_dslxt_Tree *ds, *ds2;

    if (using_partial_zip) {
      fseeko(lt->handle, 0L, SEEK_END);
      current_iter_pos = ftello(lt->handle);
      lxt2_wr_emit_u32(lt, 0);  /* size of this section (compressed)   */
      lxt2_wr_emit_u32(lt, 0);  /* size of this section (uncompressed) */
      lxt2_wr_emit_u32(lt, ~0); /* control section		       */
      fflush(lt->handle);

      lt->zhandle = gzdopen(dup(fileno(lt->handle)), lt->zmode);
      lt->zpackcount = 0;
    }

    /* fprintf(stderr, "reached granule %d, finalizing block for section %d\n",
     * lt->timegranule, lt->numsections); */
    lt->numsections++;

    /* finalize string dictionary */
    lxt2_wr_emit_u8z(lt, LXT2_WR_GRAN_SECT_DICT);

    ds = lt->dict_head;
    /* fprintf(stderr, "num_dict_entries: %d\n", lt->num_dict_entries); */
    gzflush_buffered(lt, 0);
    for (i = 0; i < lt->num_dict_entries; i++) {
      /* fprintf(stderr, "%8d %8d) '%s'\n", ds->val, i, ds->item); */
      if (ds->val != i) {
        fprintf(stderr, "internal error line %d\n", __LINE__);
        exit(255);
      }

      lxt2_wr_emit_stringz(lt, ds->item);
      ds2 = ds->next;
      free(ds->item);
      free(ds);
      ds = ds2;
    }
    lt->dict_head = lt->dict_curr = lt->dict = NULL;

    /* finalize map dictionary */
    dt = lt->mapdict_head;
    /* fprintf(stderr, "num_map_entries: %d\n", lt->num_map_entries); */
    gzflush_buffered(lt, 0);
    for (i = 0; i < lt->num_map_entries; i++) {
      /* fprintf(stderr, "+++ %08x (%d)(%d)\n", dt->item, i, dt->val); */
      if (((unsigned int)dt->val) != i) {
        fprintf(stderr, "internal error line %d\n", __LINE__);
        exit(255);
      }

#if LXT2_WR_GRANULE_SIZE > 32
      lxt2_wr_emit_u64z(lt, (dt->item >> 32) & 0xffffffff,
                        dt->item & 0xffffffff);
#else
      lxt2_wr_emit_u32z(lt, dt->item);
#endif

      dt2 = dt->next;
      free(dt);
      dt = dt2;
    }
    lt->mapdict_head = lt->mapdict_curr = lt->mapdict = NULL;

    lxt2_wr_emit_u32z(lt, lt->num_dict_entries);         /* -12 */
    lxt2_wr_emit_u32z(lt, lt->dict_string_mem_required); /* -8 */
    lxt2_wr_emit_u32z(lt, lt->num_map_entries);          /* -4 */

    lt->num_map_entries = 0;
    lt->num_dict_entries = lt->dict_string_mem_required = 0;

    /* fprintf(stderr, "returned from finalize..\n"); */

    if (using_partial_zip) {
      off_t c_len;

      gzflush_buffered(lt, 1);
      fseeko(lt->handle, 0L, SEEK_END);
      lt->position = ftello(lt->handle);

      c_len = lt->position - current_iter_pos - 12;
      fseeko(lt->handle, current_iter_pos, SEEK_SET);

      lt->zpackcount_cumulative += lt->zpackcount;
      lxt2_wr_emit_u32(lt, c_len);
      lxt2_wr_emit_u32(lt, lt->zpackcount);
    } else {
      gzflush_buffered(lt, 1);
    }

    fseeko(lt->handle, 0L, SEEK_END);
    lt->position = ftello(lt->handle);
    /* fprintf(stderr, "file position after dumping dict: %d 0x%08x\n",
     * lt->position, lt->position); */

    unclen = lt->zpackcount;
    clen = lt->position - lt->current_chunkz;

    /* fprintf(stderr, "%d/%d un/compressed bytes in section\n", unclen, clen);
     */

    fseeko(lt->handle, lt->current_chunk, SEEK_SET);
    if (using_partial_zip) {
      lxt2_wr_emit_u32(lt, lt->zpackcount_cumulative);
      lxt2_wr_emit_u32(lt, clen);
    } else {
      lxt2_wr_emit_u32(lt, unclen);
      lxt2_wr_emit_u32(lt, clen);
    }
    lxt2_wr_emit_u64(lt, (lt->firsttime >> 32) & 0xffffffff,
                     lt->firsttime & 0xffffffff);
    lxt2_wr_emit_u64(lt, (lt->lasttime >> 32) & 0xffffffff,
                     lt->lasttime & 0xffffffff);

    /* fprintf(stderr, "start: %lld, end %lld\n", lt->firsttime, lt->lasttime);
     */

    lt->timegranule = 0;
    lt->numblock++;
  }

  if (do_finalize) {
    lt->flush_valid = 1;
    lt->flushtime = lt->lasttime;
  }
}

int lxt2_wr_set_time64(struct lxt2_wr_trace *lt, lxttime_t timeval) {
  int rc = 0;

  if (lt) {
    if (lt->timeset) {
      if (timeval > lt->maxtime) {
        if (lt->bumptime) {
          lt->bumptime = 0;

          if (!lt->flush_valid) {
            lt->timepos++;
          } else {
            lt->flush_valid = 0;
          }

          if (lt->timepos == LXT2_WR_GRANULE_SIZE) {
            /* fprintf(stderr, "flushing granule to disk at time %d\n",
             * (unsigned int)timeval); */
            lxt2_wr_flush_granule(lt, 0);
          }
        }

        /* fprintf(stderr, "updating time to %d (%d dict entries/%d bytes)\n",
         * (unsigned int)timeval, lt->num_dict_entries,
         * lt->dict_string_mem_required); */
        lt->timetable[lt->timepos] = timeval;
        lt->lasttime = timeval;
      }
    } else {
      lt->timeset = 1;
      lt->mintime = lt->maxtime = timeval;

      lt->timetable[lt->timepos] = timeval;
    }

    if ((!lt->timepos) && (!lt->timegranule)) {
      lt->firsttime = timeval;
      lt->lasttime = timeval;
    }

    if ((!lt->timepos) && (!lt->timegranule) &&
        ((!lt->numblock) || (!lt->no_checkpoint))) {
      /* fprintf(stderr, "initial value burst timepos==0, timegranule==0\n"); */
      if (lt->blackout) {
        lt->blackout = 0;
        lxt2_wr_set_dumpoff(lt);
      } else {
        struct lxt2_wr_symbol *s = lt->symchain;
        while (s) {
          if ((!(s->flags & LXT2_WR_SYM_F_ALIAS)) && (s->rows < 2)) {
            if (!(s->flags & (LXT2_WR_SYM_F_DOUBLE | LXT2_WR_SYM_F_STRING))) {
              lxt2_wr_emit_value_bit_string(lt, s, 0, s->value);
            } else if (s->flags & LXT2_WR_SYM_F_DOUBLE) {
              double value = 0;

              sscanf(s->value, "%lg", &value);
              errno = 0;
              lxt2_wr_emit_value_double(lt, s, 0, value);
            } else if (s->flags & LXT2_WR_SYM_F_STRING) {
              lxt2_wr_emit_value_string(lt, s, 0, s->value);
            }
          }
          s = s->symchain;
        }
      }
      /* fprintf(stderr, "done initial value burst timepos==0,
       * timegranule==0\n"); */
    }

    lt->granule_dirty = 1;
    rc = 1;
  }

  return (rc);
}

/*
 * sets trace timescale as 10**x seconds
 */
void lxt2_wr_set_timescale(struct lxt2_wr_trace *lt, int timescale) {
  if (lt) {
    lt->timescale = timescale;
  }
}

/*
 * set number of granules per section
 * (can modify dynamically)
 */
void lxt2_wr_set_maxgranule(struct lxt2_wr_trace *lt, unsigned int maxgranule) {
  if (lt) {
    if (!maxgranule) maxgranule = ~0;
    lt->maxgranule = maxgranule;
  }
}

/*
 * Sets bracket stripping (useful for VCD conversions of
 * bitblasted nets)
 */
void lxt2_wr_symbol_bracket_stripping(struct lxt2_wr_trace *lt, int doit) {
  if (lt) {
    lt->do_strip_brackets = (doit != 0);
  }
}

static char *lxt2_wr_expand_integer_to_bits(unsigned int len, int value) {
  static char s[33];
  char *p = s;
  unsigned int i;

  if (len > 32) len = 32;

  len--;

  for (i = 0; i <= len; i++) {
    *(p++) = '0' | ((value & (1 << (len - i))) != 0);
  }
  *p = 0;

  return (s);
}

int lxt2_wr_emit_value_int(struct lxt2_wr_trace *lt, struct lxt2_wr_symbol *s,
                           unsigned int row, int value) {
  int rc = 0;

  if ((!lt) || (lt->blackout) || (!s) || (row)) return (rc);

  return (lxt2_wr_emit_value_bit_string(
      lt, s, row, lxt2_wr_expand_integer_to_bits(s->len, value)));
}

int lxt2_wr_emit_value_double(struct lxt2_wr_trace *lt,
                              struct lxt2_wr_symbol *s, unsigned int row,
                              double value) {
  int rc = 0;

  if ((!lt) || (lt->blackout) || (!s) || (row)) return (rc);

  if (!lt->emitted) {
    lxt2_wr_emitfacs(lt);
    lt->emitted = 1;

    if (!lt->timeset) {
      lxt2_wr_set_time(lt, 0);
    }
  }

  while (s->aliased_to) /* find root alias if exists */
  {
    s = s->aliased_to;
  }

  if (s->flags & LXT2_WR_SYM_F_DOUBLE) {
    char d_buf[32];
    unsigned int idx;

    rc = 1;
    sprintf(d_buf, "%.16g", value);
    if (!strcmp(d_buf, s->value)) return (rc);

    lt->bumptime = 1;
    free(s->value);
    s->value = strdup(d_buf);

    lt->dict = lxt2_wr_dslxt_splay(s->value, lt->dict);

    if (!lxt2_wr_dslxt_success) {
      unsigned int vlen = strlen(d_buf) + 1;
      char *vcopy = (char *)malloc(vlen);
      strcpy(vcopy, d_buf);
      lt->dict_string_mem_required += vlen;
      lt->dict = lxt2_wr_dslxt_insert(vcopy, lt->dict, lt->num_dict_entries);

      if (lt->dict_curr) {
        lt->dict_curr->next = lt->dict;
        lt->dict_curr = lt->dict;
      } else {
        lt->dict_head = lt->dict_curr = lt->dict;
      }

      idx = lt->num_dict_entries + LXT2_WR_DICT_START;
      lt->num_dict_entries++;
    } else {
      idx = lt->dict->val + LXT2_WR_DICT_START;
    }

    if ((s->msk & (LXT2_WR_GRAN_1VAL << lt->timepos)) == LXT2_WR_GRAN_0VAL) {
      s->msk |= (LXT2_WR_GRAN_1VAL << lt->timepos);
      s->chg[s->chgpos] = idx;

      s->chgpos++;
    } else {
      s->chg[s->chgpos - 1] = idx;
    }

    lt->granule_dirty = 1;
  }

  return (rc);
}

int lxt2_wr_emit_value_string(struct lxt2_wr_trace *lt,
                              struct lxt2_wr_symbol *s, unsigned int row,
                              char *value) {
  int rc = 0;

  if ((!lt) || (lt->blackout) || (!s) || (!value) || (row)) return (rc);

  if (!lt->emitted) {
    lxt2_wr_emitfacs(lt);
    lt->emitted = 1;

    if (!lt->timeset) {
      lxt2_wr_set_time(lt, 0);
    }
  }

  while (s->aliased_to) /* find root alias if exists */
  {
    s = s->aliased_to;
  }

  if (s->flags & LXT2_WR_SYM_F_STRING) {
    unsigned int idx;

    rc = 1;
    if (!strcmp(value, s->value)) return (rc);

    lt->bumptime = 1;
    free(s->value);
    s->value = strdup(value);

    lt->dict = lxt2_wr_dslxt_splay(s->value, lt->dict);

    if (!lxt2_wr_dslxt_success) {
      unsigned int vlen = strlen(value) + 1;
      char *vcopy = (char *)malloc(vlen);
      strcpy(vcopy, value);
      lt->dict_string_mem_required += vlen;
      lt->dict = lxt2_wr_dslxt_insert(vcopy, lt->dict, lt->num_dict_entries);

      if (lt->dict_curr) {
        lt->dict_curr->next = lt->dict;
        lt->dict_curr = lt->dict;
      } else {
        lt->dict_head = lt->dict_curr = lt->dict;
      }

      idx = lt->num_dict_entries + LXT2_WR_DICT_START;
      lt->num_dict_entries++;
    } else {
      idx = lt->dict->val + LXT2_WR_DICT_START;
    }

    if ((s->msk & (LXT2_WR_GRAN_1VAL << lt->timepos)) == LXT2_WR_GRAN_0VAL) {
      s->msk |= (LXT2_WR_GRAN_1VAL << lt->timepos);
      s->chg[s->chgpos] = idx;

      s->chgpos++;
    } else {
      s->chg[s->chgpos - 1] = idx;
    }

    lt->granule_dirty = 1;
  }

  return (rc);
}

int lxt2_wr_emit_value_bit_string(struct lxt2_wr_trace *lt,
                                  struct lxt2_wr_symbol *s, unsigned int row,
                                  char *value) {
  int rc = 0;
  char *vpnt;
  char *vfix;
  int valuelen;
  int i;

  if ((!lt) || (lt->blackout) || (!s) || (!value) || (!*value) || (row))
    return (rc);

  if (!lt->emitted) {
    lxt2_wr_emitfacs(lt);
    lt->emitted = 1;

    if (!lt->timeset) {
      lxt2_wr_set_time(lt, 0);
    }
  }

  while (s->aliased_to) /* find root alias if exists */
  {
    s = s->aliased_to;
  }

  valuelen = strlen(value); /* ensure string is proper length */
  if (valuelen == s->len) {
    vfix = (char *)wave_alloca(s->len + 1);
    strcpy(vfix, value);
    value = vfix;
  } else {
    vfix = (char *)wave_alloca(s->len + 1);

    if (valuelen < s->len) {
      int lendelta = s->len - valuelen;
      memset(vfix, (value[0] != '1') ? value[0] : '0', lendelta);
      strcpy(vfix + lendelta, value);
    } else {
      memcpy(vfix, value, s->len);
      vfix[s->len] = 0;
    }

    value = vfix;
  }

  for (i = 0; i < s->len; i++) {
    unsigned char ch = value[i];
    if ((ch >= 'A') && (ch <= 'Z')) value[i] = ch + ('a' - 'A');
  }

  if ((lt->timepos || lt->timegranule) && !strcmp(value, s->value)) {
    return (1); /* redundant value change */
  }

  if (!(s->flags & (LXT2_WR_SYM_F_DOUBLE | LXT2_WR_SYM_F_STRING))) {
    char prevch;
    int idx;

    lt->bumptime = 1;

    vpnt = value;
    prevch = *vpnt;
    while (*vpnt) {
      if (prevch == *vpnt) {
        vpnt++;
      } else {
        prevch = 0;
        break;
      }
    }

    switch (prevch) {
      case '0':
        idx = LXT2_WR_ENC_0;
        break;
      case '1':
        idx = LXT2_WR_ENC_1;
        break;
      case 'X':
      case 'x':
        idx = LXT2_WR_ENC_X;
        break;
      case 'Z':
      case 'z':
        idx = LXT2_WR_ENC_Z;
        break;
      default:
        idx = -1;
        break;
    }

    if ((lt->timepos) || (lt->timegranule)) {
      for (i = 0; i < s->len; i++) {
        char ch = value[i];

        switch (ch) {
          case '0':
            if (s->value[i] != '1')
              goto nextalg;
            else
              break;
          case '1':
            if (s->value[i] != '0')
              goto nextalg;
            else
              break;
          default:
            goto nextalg;
        }
      }
      idx = LXT2_WR_ENC_INV;
      goto do_enc;

    nextalg:
      if (s->len > 1) {
        if (!memcmp(s->value + 1, value, s->len - 1)) {
          if ((value[s->len - 1] & 0xfe) == '0') {
            idx = LXT2_WR_ENC_LSH0 + (value[s->len - 1] & 0x01);
            goto do_enc;
          }
        } else if (!memcmp(s->value, value + 1, s->len - 1)) {
          if ((value[0] & 0xfe) == '0') {
            idx = LXT2_WR_ENC_RSH0 + (value[0] & 0x01);
            goto do_enc;
          }
        }

        if (s->len <= 32) {
          unsigned int intval_old = 0, intval_new = 0;
          unsigned int msk;

          for (i = 0; i < s->len; i++) {
            char ch = value[i];
            if ((ch != '0') && (ch != '1')) goto idxchk;
            intval_new <<= 1;
            intval_new |= ((unsigned int)(ch & 1));

            ch = s->value[i];
            if ((ch != '0') && (ch != '1')) goto idxchk;
            intval_old <<= 1;
            intval_old |= ((unsigned int)(ch & 1));
          }

          msk = (~0) >> (32 - s->len);
          if (((intval_old + 1) & msk) == intval_new) {
            idx = LXT2_WR_ENC_ADD1;
            goto do_enc;
          }
          if (((intval_old - 1) & msk) == intval_new) {
            idx = LXT2_WR_ENC_SUB1;
            goto do_enc;
          }

          if (((intval_old + 2) & msk) == intval_new) {
            idx = LXT2_WR_ENC_ADD2;
            goto do_enc;
          }
          if (((intval_old - 2) & msk) == intval_new) {
            idx = LXT2_WR_ENC_SUB2;
            goto do_enc;
          }

          if (((intval_old + 3) & msk) == intval_new) {
            idx = LXT2_WR_ENC_ADD3;
            goto do_enc;
          }
          if (((intval_old - 3) & msk) == intval_new) {
            idx = LXT2_WR_ENC_SUB3;
            goto do_enc;
          }

          if (s->len > 2) {
            if (((intval_old + 4) & msk) == intval_new) {
              idx = LXT2_WR_ENC_ADD4;
              goto do_enc;
            }
            if (((intval_old - 4) & msk) == intval_new) {
              idx = LXT2_WR_ENC_SUB4;
              goto do_enc;
            }
          }
        }
      }
    }

  idxchk:
    if (idx < 0) {
      vpnt = lxt2_wr_vcd_truncate_bitvec(value);
      lt->dict = lxt2_wr_dslxt_splay(vpnt, lt->dict);

      if (!lxt2_wr_dslxt_success) {
        unsigned int vlen = strlen(vpnt) + 1;
        char *vcopy = (char *)malloc(vlen);
        strcpy(vcopy, vpnt);
        lt->dict_string_mem_required += vlen;
        lt->dict = lxt2_wr_dslxt_insert(vcopy, lt->dict, lt->num_dict_entries);

        if (lt->dict_curr) {
          lt->dict_curr->next = lt->dict;
          lt->dict_curr = lt->dict;
        } else {
          lt->dict_head = lt->dict_curr = lt->dict;
        }

        idx = lt->num_dict_entries + LXT2_WR_DICT_START;
        lt->num_dict_entries++;
      } else {
        idx = lt->dict->val + LXT2_WR_DICT_START;
      }
    }

  do_enc:
    if ((s->msk & (LXT2_WR_GRAN_1VAL << lt->timepos)) == LXT2_WR_GRAN_0VAL) {
      s->msk |= (LXT2_WR_GRAN_1VAL << lt->timepos);
      s->chg[s->chgpos] = idx;

      s->chgpos++;
    } else {
      s->chg[s->chgpos - 1] = idx;
    }

    strncpy(s->value, value, s->len);

    lt->granule_dirty = 1;
  }

  return (rc);
}

/*
 * dumping control
 */
void lxt2_wr_set_dumpoff(struct lxt2_wr_trace *lt) {
  struct lxt2_wr_symbol *s;

  if ((lt) && (!lt->blackout)) {
    if (!lt->emitted) {
      lxt2_wr_emitfacs(lt);
      lt->emitted = 1;

      if (!lt->timeset) {
        lxt2_wr_set_time(lt, 0);
      }
    }

    s = lt->symchain;
    while (s) {
      if (!(s->flags & LXT2_WR_SYM_F_ALIAS)) {
        if ((s->msk & (LXT2_WR_GRAN_1VAL << lt->timepos)) ==
            LXT2_WR_GRAN_0VAL) {
          s->msk |= (LXT2_WR_GRAN_1VAL << lt->timepos);
          s->chg[s->chgpos] = LXT2_WR_ENC_BLACKOUT;

          s->chgpos++;
        } else {
          s->chg[s->chgpos - 1] = LXT2_WR_ENC_BLACKOUT;
        }
      }

      s = s->symchain;
    }

    lt->bumptime = 1;
    lt->blackout = 1;
    lt->granule_dirty = 1;
  }
}

void lxt2_wr_set_dumpon(struct lxt2_wr_trace *lt) {
  int i;
  struct lxt2_wr_symbol *s;

  if ((lt) && (lt->blackout)) {
    lt->blackout = 0;

    s = lt->symchain;
    while (s) {
      if (!(s->flags & LXT2_WR_SYM_F_ALIAS)) {
        if (s->flags & LXT2_WR_SYM_F_DOUBLE) {
          free(s->value);
          s->value = strdup("0"); /* will cause mismatch then flush */
        } else {
          if (!(s->flags & LXT2_WR_SYM_F_STRING)) {
            s->value[0] = '-'; /* will cause mismatch then flush */
            for (i = 1; i < s->len; i++) {
              s->value[i] = 'x'; /* initial value */
            }
            s->value[i] = 0;
          } else {
            free(s->value);
            s->value = (char *)calloc(1, 1 * sizeof(char));
          }
        }
      }

      s = s->symchain;
    }

    s = lt->symchain;
    while (s) {
      if ((!(s->flags & LXT2_WR_SYM_F_ALIAS)) && (s->rows < 2)) {
        char tmp[16]; /* To get rid of the warning */
        if (!(s->flags & (LXT2_WR_SYM_F_DOUBLE | LXT2_WR_SYM_F_STRING))) {
          strcpy(tmp, "x");
          lxt2_wr_emit_value_bit_string(lt, s, 0, tmp);
        } else if (s->flags & LXT2_WR_SYM_F_DOUBLE) {
          double value;
          sscanf("NaN", "%lg", &value);
          lxt2_wr_emit_value_double(lt, s, 0, value);
        } else if (s->flags & LXT2_WR_SYM_F_STRING) {
          strcpy(tmp, "UNDEF");
          lxt2_wr_emit_value_string(lt, s, 0, tmp);
        }
      }
      s = s->symchain;
    }
  }
}

/*
 * flush the trace...
 */
void lxt2_wr_flush(struct lxt2_wr_trace *lt) {
  if (lt) {
    if ((lt->timegranule) || (lt->timepos > 0)) {
      if (lt->granule_dirty) {
        lt->timepos++;
        lxt2_wr_flush_granule(lt, 1);
      }
    }
  }
}

/*
 * close out the trace and fixate it
 */
void lxt2_wr_close(struct lxt2_wr_trace *lt) {
  if (lt) {
    if (lt->granule_dirty) {
      lt->timepos++;
      lxt2_wr_flush_granule(lt, 1);
    }

    if (lt->symchain) {
      struct lxt2_wr_symbol *s = lt->symchain;
      struct lxt2_wr_symbol *s2;

      while (s) {
        free(s->name);
        free(s->value);
        s2 = s->symchain;
        free(s);
        s = s2;
      }

      lt->symchain = NULL;
    }

    free(lt->lxtname);
    free(lt->sorted_facs);
    fclose(lt->handle);
    free(lt);
  }
}

/*
 * set compression depth
 */
void lxt2_wr_set_compression_depth(struct lxt2_wr_trace *lt,
                                   unsigned int depth) {
  if (lt) {
    if (depth > 9) depth = 9;
    sprintf(lt->zmode, "wb%u", depth);
  }
}

/*
 * time zero offset
 */
void lxt2_wr_set_timezero(struct lxt2_wr_trace *lt, lxtstime_t timeval) {
  if (lt) {
    lt->timezero = timeval;
  }
}
